Technote PR 10 | October 1990 |
Changes since October 1990: Added code in both versions to handle printing
documents larger than 128 pages.
At one time, Apple recommended that developers call _PrOpen at the beginning of their application and _PrClose at the end, before returning to the Finder. This recommendation was in the ancient past when an application only had to deal with a single printer driver.
As more printer drivers became available, it became important for an
application to consider the presence of other applications and how opening and
closing the printer driver affected them. The user could open the Chooser at
any time and change the current printer driver without the current
application's knowledge. If an application followed the old philosophy and a
user changed the current printer driver while running the application, the next
time the user attempted to print, the wrong driver would be open, the Printing
Manager would not be able to find the necessary resources, and the user would
get an error.
DTS currently recommends that applications open and close the printer driver each time the application uses the Printing Manager.
MPW Pascal
*------ PrintStuff ----------------------------------------------------------------*} {** ** PrintStuff will call all of the necessary Print Manager calls to print ** a document. It checks PrError() after each Print Manager call. If an ** error is found, all of the Print Manager open calls (i.e., PrOpen, ** PrOpenDoc...) will have a corresponding close call before the error ** is posted to the user. You want to use this approach to make sure the ** Print Manager closes properly and all temporary memory is released. **} PROCEDURE PrintStuff; VAR copies, firstPage, lastPage, loop, numberOfCopies, pageNumber, printmgrsResFile, realNumberOfPagesInDoc : Integer; PrintError : LongInt; oldPort : GrafPtr; thePrRecHdl : THPrint; thePrPort : TPPrPort; theStatus : TPrStatus; BEGIN GetPort(oldPort); {** UnLoadTheWorld will swap out ALL unneeded code segments and data that are NOT required during print time. Your print code must be a separate code segment. **} UnLoadTheWorld; thePrRecHdl := THPrint(NewHandle(SIZEOF(TPrint))); IF (MemError = noErr) AND (thePrRecHdl <> NIL) THEN BEGIN PrOpen; IF (PrError = noErr) THEN BEGIN {** Save the current resource file (i.e. the printer driver's) so the driver will not lose its resources upon return from the pIdleProc. **} printmgrsResFile := CurResFile; PrintDefault(thePrRecHdl); IF (PrError = noErr) THEN BEGIN IF (PrStlDialog(thePrRecHdl)) THEN BEGIN {** DetermineNumberOfPagesinDoc determines the number of pages contained in the document by comparing the size of the document with rPage from the TPrInfo record (IM II-150). It returns the number of pages required to print the document for the currently selected printer. **} realNumberOfPagesinDoc := DetermineNumberOfPagesinDoc (thePrRecHdl^^.prInfo.rPage); IF (PrJobDialog(thePrRecHdl)) THEN BEGIN {** Get the number of copies of the document that the user wants printed from iCopies of the TPrJob record (IM II-151). **} numberOfCopies := thePrRecHdl^^.prJob.iCopies; {** Get the first and last pages of the document that were requested to be printed by the user from iFstPage and iLastPage from the TPrJob record (IM II-151). **} firstPage := thePrRecHdl^^.prJob.iFstPage; lastPage := thePrRecHdl^^.prJob.iLstPage; {** Print "all" pages in the print loop **} thePrRecHdl^^.prJob.iFstPage := 1; thePrRecHdl^^.prJob.iLstPage := 9999; {** Determine the "real" number of pages contained in the document. Without this test, you would print 9999 pages. **} IF (realNumberOfPagesinDoc < lastPage) THEN lastPage := realNumberOfPagesinDoc; PrintingStatusDialog := GetNewDialog(257, NIL, POINTER(-1)); {** Print the number of copies of document requested by the user from the Print Job Dialog. **} For copies := 1 To numberOfCopies Do BEGIN {** Install a pointer to your pIdle proc in my print record. **} thePrRecHdl^^.prJob.pIdleProc := @checkMyPrintDialogButton; {** Restore the resource file to the printer driver's. **} UseResFile(printmgrsResFile); thePrPort := PrOpenDoc(thePrRecHdl, NIL, NIL); IF (PrError = noErr) THEN BEGIN {** Print the range of pages of the document requested by the user from the Print Job Dialog. **} pageNumber := firstPage; WHILE ((pageNumber <= lastPage) AND (PrError = noErr)) DO BEGIN { ** If we've crossed a 128-page boundary, close the current print file, send it to the printer if necessary, and open a new document. ** } IF (pageNumber - firstPage) MOD iPFMaxPgs = 0 THEN BEGIN IF pageNumber <> firstPage THEN BEGIN PrCloseDoc(thePrPort); IF (thePrRecHdl^^.prJob.bJDocLoop = bSpoolLoop) AND (PrError = noErr) THEN PrPicFile(thePrRecHdl, NIL,NIL, NIL, theStatus); thePrPort := PrOpenDoc(thePrRecHdl, NIL, NIL); END; END; PrOpenPage(thePrPort, NIL); IF (PrError = noErr) THEN BEGIN {** rPage (IM II-150) is the printable area for the currently selected printer. By passing the current enables your app to use the same routine to draw to the screen and the printer's GrafPort. **} DrawStuff (thePrRecHdl^^.prInfo.rPage, GrafPtr (thePrPort), pageNumber); END; PrClosePage(thePrPort); pageNumber := pageNumber + 1; END; {** End pagenumber loop **} END; PrCloseDoc(thePrPort); END; {** End copies loop **} {** The printing job is being canceled by the request of the user from the Print Style Dialog or the Print Job Dialog. PrError will be set to iPrAbort to tell the Print Manager to abort the current printing job. **} END ELSE PrSetError(iPrAbort); {** Cancel from the job dialog **} END ELSE PrSetError(iPrAbort); {** Cancel from the style dialog **} END; END; IF (thePrRecHdl^^.prJob.bJDocLoop = bSpoolLoop) and (PrError = noErr) THEN PrPicFile(thePrRecHdl, NIL, NIL, NIL, theStatus); {** Grab the printing error -- once you close the Printing Manager, PrError doesn't return a valid result anymore. **} PrintError := PrError; PrClose; {** You do not want to report any printing errors until you have fallen through the printing loop. This will make sure that ALL of the Print Manager's open calls have their corresponding close calls, thereby enabling the Print Manager to close properly and that all temporary memory allocations are released. **} IF (PrintError <> noErr) THEN PostPrintingErrors (PrintError); END; IF (thePrRecHdl <> NIL) THEN DisposHandle(Handle (thePrRecHdl)); IF (PrintingStatusDialog <> NIL) THEN DisposDialog(PrintingStatusDialog); SetPort(oldPort); END; {** PrintStuff **}
MPW C
/*------ PrintStuff ----------------------------------------------------------------*/ ** ** PrintStuff will call all of the necessary Print Manager calls to print ** a document. It checks PrError() after each Print Manager call. If an error ** is found, all of the Print Manager open calls (i.e., PrOpen, PrOpenDoc...) ** will have a corresponding close call before the error is posted to the user. ** You want to use this approach to make sure the Print Manager closes properly ** and all temporary memory is released. **/ void PrintStuff () { GrafPtr oldPort; short copies, firstPage, lastPage, numberOfCopies, printmgrsResFile, realNumberOfPagesinDoc, pageNumber, PrintError; THPrint thePrRecHdl; TPPrPort thePrPort; TPrStatus theStatus; GetPort(&oldPort); /** UnLoadTheWorld will swap out ALL unneeded code segments and data that are NOT required during print time. Your print code must be a separate code segment. **/ UnLoadTheWorld (); thePrRecHdl = (THPrint) NewHandle (sizeof (TPrint)); /** Check to make sure that the memory manager did not produce an error when it allocated the print record handle and make sure it did not pass back a nil handle. **/ if (MemError() == noErr && thePrRecHdl != nil) { PrOpen(); if (PrError() == noErr) { /** Save the current resource file (i.e. the printer driver's) so the driver will not lose its resources upon return from the pIdleProc. **/ printmgrsResFile = CurResFile(); PrintDefault(thePrRecHdl); if (PrError() == noErr) { if (PrStlDialog(thePrRecHdl)) { /** DetermineNumberOfPagesinDoc determines the number of pages contained in the document by comparing the size of the document with rPage from the TPrInfo record (IM II-150). It returns the number of pages required to print the document for the currently selected printer. **/ realNumberOfPagesinDoc = DetermineNumberOfPagesinDoc ((**thePrRecHdl).prInfo.rPage); if (PrJobDialog(thePrRecHdl)) { /** Get the number of copies of the document that the user wants printed from iCopies of the TPrJob record (IM II-151). **/ numberOfCopies = (**thePrRecHdl).prJob.iCopies; /** Get the first and last pages of the document that were requested to be printed by the user from iFstPage and iLastPage from the TPrJob record (IM II-151). **/ firstPage = (**thePrRecHdl).prJob.iFstPage; lastPage = (**thePrRecHdl).prJob.iLstPage; /** Print "all" pages in the print loop **/ (**thePrRecHdl).prJob.iFstPage = 1; (**thePrRecHdl).prJob.iLstPage = 9999; /** Determine the "real" number of pages contained in the document. Without this test, you would print 9999 pages. **/ if (realNumberOfPagesinDoc < lastPage) lastPage = realNumberOfPagesinDoc; PrintingStatusDialog = GetNewDialog(257, nil, (WindowPtr) -1); /** Print the number of copies of the document requested by the user from the Print Job Dialog. **/ for (copies = 1; copies <= numberOfCopies; copies++) { /** Install a pointer to your pIdle proc in my print record. **/ (**thePrRecHdl).prJob.pIdleProc = checkMyPrintDialogButton; /** Restore the resource file to the printer driver's. **/ UseResFile(printmgrsResFile); thePrPort = PrOpenDoc(thePrRecHdl, nil, nil); if (PrError() == noErr) { /** Print the range of pages of the document requested by the user from the Print Job Dialog. **/ pageNumber = firstPage; while (pageNumber <= lastPage && PrError() == noErr) { /** If we've crossed a 128-page boundary, close the current print file, send it to the printer if necessary, and open a new document. **/ if ((pageNumber - firstPage) % iPFMaxPgs == 0) { if (pageNumber != firstPage) { PrCloseDoc(thePrPort); if (((**thePrRecHdl).prJob.bJDocLoop == bSpoolLoop) && (PrError() == noErr)) PrPicFile(thePrRecHdl, nil, nil, nil, &theStatus); thePrPort = PrOpenDoc(thePrRecHdl, nil, nil); } } PrOpenPage(thePrPort, nil); if (PrError() == noErr) { /** rPage (IM II-150) is the printable area for the currently selected printer. By passing the current port to the draw routine, enables your app to use the same routine to draw to the screen and the printer's GrafPort. **/ DrawStuff ((**thePrRecHdl).prInfo.rPage, (GrafPtr) thePrPort, pageNumber); } PrClosePage(thePrPort); pageNumber++; } /** End pageNumber loop **/ } PrCloseDoc(thePrPort); } /** End copies loop **/ } /** The printing job is being canceled by the request of the user from the Print Style Dialog or the Print Job Dialog. PrError will be set to PrAbort to tell the Print Manager to abort the current printing job. **/ else PrSetError (iPrAbort); /** cancel from the job dialog **/ } else PrSetError (iPrAbort); /** cancel from the style dialog **/ } } if (((**thePrRecHdl).prJob.bJDocLoop == bSpoolLoop) && (PrError() == noErr)) PrPicFile(thePrRecHdl, nil, nil, nil, &theStatus); /** Grab the printing error -- once you close the Printing Manager, PrError doesn't returna valid result anymore. **/ PrintError = PrError(); PrClose(); /** You do not want to report any printing errors until you have fallen through the printing loop. This will make sure that ALL of the Print Manager's open calls have their corresponding close calls, thereby enabling the Print Manager to close properly and that all temporary memory allocations are released. **/ if (PrintError != noErr) PostPrintingErrors (PrintError); } if (thePrRecHdl != nil) DisposHandle((Handle) thePrRecHdl); if (PrintingStatusDialog != nil) DisposDialog(PrintingStatusDialog); SetPort(oldPort); } /** PrintStuff **/
An application should always check for error conditions while printing by calling PrError. PrError returns errors from the Printing Manager (and some AppleTalk and OS errors) that occur during printing.
As the example code demonstrates, an application should call PrError after each call to a Printing Manager function or procedure. By consistently checking PrError after each call, the application is able to catch any errors created at print time and report them to a user via a dialog box in a clean and graceful manner.
The following section outlines some general error-handling guidelines.
* You should avoid calling PrError within your pIdle procedure; errors that occur while it is executing are usually temporary and serve only as internal flags for communication within the printer driver--they are not intended for the application. If you absolutely must call PrError within your idle procedure, and an error occurs, never abort printing within the idle procedure itself. Wait until the last called printing procedure returns, then check to see if the error still remains. Attempting to abort printing within an idle procedure is a guarantee of certain death.
* Upon detecting an error after the completion of a printing routine, stop drawing at that point, and proceed to the next procedure to close any previously made open calls. For example, if you detect an error after calling PrOpenDoc, skip to the next PrCloseDoc. Or, if you get an error after calling PrOpenPage, skip to the next PrClosePage and PrCloseDoc. Remember that if you have called PrOpen, then you must call the corresponding PrClose to ensure that printing closes properly and all temporary memory allocations are released and returned to the heap.
* Do not display any alert or dialog boxes to report an error until the end of the printing loop. Once at the end, check for the error again; if there is no error assume that printing completed normally. If the error is still present, then you can alert the user.
This technique is important for two reasons. First, if you display a dialog box in the middle of the printing loop, it could cause errors that can terminate an otherwise normal job. For example, if the printer is an AppleTalk printer, the connection can be terminated abnormally since the driver would be unable to respond to AppleTalk requests received from the printer while the dialog box was waiting for input from the user. If the printer does not hear from the Macintosh with a short period of time (e.g., 30 seconds), it times out, assuming that the Macintosh is no longer there, which results in a prematurely broken connection causing another error to which the application must respond.
In addition, the driver may have already displayed its own dialog box in response to an error. In this instance, the driver posts an error to let the application know that something went wrong and it should abort printing. For example, when the LaserWriter driver detects that the Laser Prep version which has been downloaded to the LaserWriter is different than that with which the user is trying to print, it displays the appropriate dialog box informing the user of the situation and giving him the option of reinitializing the printer. If the user chooses to cancel printing, the driver posts an error to let the application know that it needs to abort, but since the driver has already taken care of the error by displaying a dialog box, the error is reset to zero before the printing loop is complete. The application should check for the error again at the end of the printing loop, and if it still indicates an error, the application can then display the appropriate dialog box.
* If using PrGeneral, be prepared to receive the following errors: NoSuchRsl, OpNotImpl, and resNotFound. In all three cases, the application should be prepared to continue to print without using the features of that particular opcode.
However, in the case of the resNotFound error, it means the current printer driver does not support PrGeneral. This lack of support should not be a problem for an application, but it needs to be prepared to deal with this error. If you receive a resNotFound error from PrError, clear the error with a call to PrSetError(0); otherwise, PrError might still contain this error the next time you check it, which would prevent your application from printing.
If you install a procedure for handling requests to cancel printing, with an option to pause the printing process, beware of timeout problems when printing to the LaserWriter. Communication between the Macintosh and the LaserWriter must be maintained to prevent a job or a wait timeout. If there is no communication for a period of time (over two minutes), the printer times out and the print job terminates due to a wait timeout. Or, if the print job requires more than three minutes to print, the print job terminates due to a job timeout. Since, there is no good method to determine to what type of printer an application is printing, it is probably a good idea to document the possibility of a LaserWriter timing out for a user who chooses to select "pause" for over two minutes.
The Printing Manager reports the error messages covered in this section. If an error that does not belong to the Printing Manager occurs, the Printing Manager puts it into low memory, where it can be retrieved with a call to PrError, and terminates the printing loop, if necessary. As already documented, if you encounter an error in the middle of a printing loop, do not jump out; fall through the loop and let the Printing Manager terminate properly.
The following errors are specific to the LaserWriter family:
-4101 Printer not found or closed
-4100 Connection just closed
-4099 Write request too big
-4098 Request already active
-4097 Bad connection refnum
-4096 No free Connect Control Blocks (CCBs) available
-8133 PostScript error occurred during transmission of data to printer. Most often caused by a bug in the PostScript code being downloaded.
-8132 Timeout occurred. This error is returned when no data has been sent to the printer for two minutes. Usually caused by extremely long imaging time.
-8131 Printer not responding: it may have been turned "off." This error occurs if a user turns off the LaserWriter in the middle of a print job.
The following errors are specific to PrGeneral:
1 NoSuchRsl Requested resolution is not supported
2 OpNotImpl Requested PrGeneral opcode not implemented in the current printer driver.
-192 resNotfound The current printer driver does not support PrGeneral.
The most common error encountered is -4101, which is generated if no
LaserWriter is selected. Since this error is so common, it is a good idea to
display a dialog box requesting the user to select a printer from the Chooser
when this error is encountered.
Further Reference:
Main | Page One | What's New | Apple Computer, Inc. | Find It | Contact Us | Help